#include "hikari.h"
#include <conio.h>

#pragma comment(lib, "irrlicht.lib")
#pragma comment(lib,"BulletCollision_vs2010_debug.lib")
#pragma comment(lib,"BulletDynamics_vs2010_debug.lib")
#pragma comment(lib,"LinearMath_vs2010_debug.lib")

const float UPDATE_FPS=70;
bool debugPhysics=false;
bool debugNavmesh=false;

wstring getSceneName(){
	char buff[1024];
	FILE *f;
	fopen_s(&f, "scene.dbg", "r");
	if(f==0){
		printf("Cannot open file scene.dbg");
		return L"";
	}

	fgets(buff, 1024, f);
	fclose(f);
	std::string path(buff);
	wstring wpath=Utils::string_to_wstring(path);
	return wpath;
}

void updateUpdateableObjects(HikariContext*context){
	std::list<HObj*>&updateableObjects=context->getListOfUpdateableObjects();
	for(std::list<HObj*>::iterator i=updateableObjects.begin();i!=updateableObjects.end();i++){
		HObj*object=*i;
		vector<hComponent*>components=object->getComponentList();
		int length=components.size();
		for(int i=0;i<length;i++){
			hComponent*component=components[i];
			if(component->isEnabled()){
				component->update(context, object);
			}
		}
	}
}

void updateCameras(HikariContext*context){
	vector<HObjCamera*>&cameras=context->getCameras();

	int size=cameras.size();
	for(int i=0;i<size;i++){
		cameras[i]->targetCamera();
	}
}

class DebugDraw : public btIDebugDraw
{
public:
	DebugDraw(irr::IrrlichtDevice* const device) :
	  mode(DBG_NoDebug), driver(device->getVideoDriver()), logger(device->getLogger())
	  {

	  }

	  void drawLine(const btVector3& from, const btVector3& to, const btVector3& color)
	  {
		  //workaround to bullet's inconsistent debug colors which are either from 0.0 - 1.0 or from 0.0 - 255.0
		  irr::video::SColor newColor(255, (irr::u32)color[0], (irr::u32)color[1], (irr::u32)color[2]);
		  if (color[0] <= 1.0 && color[0] > 0.0)
			  newColor.setRed((irr::u32)(color[0]*255.0));
		  if (color[1] <= 1.0 && color[1] > 0.0)
			  newColor.setGreen((irr::u32)(color[1]*255.0));
		  if (color[2] <= 1.0 && color[2] > 0.0)
			  newColor.setBlue((irr::u32)(color[2]*255.0));

		  this->driver->draw3DLine(
			  irr::core::vector3df(from[0], from[1], from[2]),
			  irr::core::vector3df(to[0], to[1], to[2]),
			  newColor);
	  }

	  void drawContactPoint(const btVector3& PointOnB, const btVector3& normalOnB, btScalar distance, int lifeTime, const btVector3& color)
	  {
		  static const irr::video::SColor CONTACTPOINT_COLOR(255, 255, 255, 0); //bullet's are black :(

		  //   this->drawLine(PointOnB, PointOnB + normalOnB*distance, CONTACTPOINT_COLOR);

		  const btVector3 to(PointOnB + normalOnB*distance);

		  this->driver->draw3DLine(
			  irr::core::vector3df(PointOnB[0], PointOnB[1], PointOnB[2]),
			  irr::core::vector3df(to[0], to[1], to[2]),
			  CONTACTPOINT_COLOR);
	  }

	  void reportErrorWarning(const char* text)
	  {
		  this->logger->log(text, irr::ELL_ERROR);
	  }

	  void draw3dText(const btVector3& location, const char* text) { }
	  void setDebugMode(int mode) { this->mode = mode; }
	  int getDebugMode() const { return this->mode; }

private:

	int mode;

	irr::video::IVideoDriver* const driver;
	irr::ILogger* logger;
};

void createPhysicsWorld(HikariContext*context){
	btBroadphaseInterface *broadPhase = new btAxisSweep3(btVector3(-1000, -1000, -1000), btVector3(1000, 1000, 1000));
	btDefaultCollisionConfiguration *collisionConfiguration = new btDefaultCollisionConfiguration();
	btCollisionDispatcher *dispatcher = new btCollisionDispatcher(collisionConfiguration);
	btSequentialImpulseConstraintSolver *solver = new btSequentialImpulseConstraintSolver();
	btDiscreteDynamicsWorld * world = new btDiscreteDynamicsWorld(dispatcher, broadPhase, solver,collisionConfiguration);
	context->setCollisionWorld(world);

	DebugDraw*debugDraw=new DebugDraw(context->getDevice());
	debugDraw->setDebugMode(btIDebugDraw::DBG_DrawWireframe |
		btIDebugDraw::DBG_DrawAabb |
		btIDebugDraw::DBG_DrawContactPoints |
		btIDebugDraw::DBG_DrawConstraints
		);
	world->setDebugDrawer(debugDraw);
}

void updateOnRenderComponents(HikariContext*context){
	std::list<HObj*>&updateableObjects=context->getListOfRenderUpdateableObjects();
	for(std::list<HObj*>::iterator i=updateableObjects.begin();i!=updateableObjects.end();i++){
		HObj*object=*i;
		vector<hComponent*>components=object->getRenderComponentList();
		int length=components.size();
		for(int i=0;i<length;i++){
			hComponent*component=components[i];
			if(component->isEnabled()){
				component->onRender(context, object);
			}
		}
	}
}

void updatePhysicsWorld(HikariContext*context){
	context->getCollisionWorld()->stepSimulation(
		context->getDeltaTime(), 60);
}

DECLARE_REFLECTION_DATABASE

int main(int argc, char* argv[]){
	try{
	IrrlichtInitializer initializer;
	IrrlichtDevice*device=initializer.createIrrlichtDevice(L"game.hikari");
	device->setWindowCaption(L"Hikari");
	if(device==0){
		printf("Cannot initialize engine device\n");
		_getch();
		return 1;
	}

	HikariEventReceiver eventReceiver;
	HikariContext context(device, &eventReceiver);
	gui::IGUIEnvironment*gui=device->getGUIEnvironment();
	device->setEventReceiver(&eventReceiver);
	createPhysicsWorld(&context);

	wstring sceneName=getSceneName();
	if(Utils::equal(sceneName,L"")){
		_getch();
		return 1;
	}

	bool result=Hikari::loadScene(sceneName, &context);
	if(result==false){
		printf("Error while loading the scene\n");
		return 1;
	}

	CTimer fpsTimer;
	
	ITimer*timer=device->getTimer();
	u32 currentTime=timer->getRealTime();
	u32 timeOfLastUpdate=currentTime;
	int frameCounter=0;

	//
	f32 updSumm=0;
	//

	float milliseccondsPerUpdate=(1.f/UPDATE_FPS);
	gui::IGUIStaticText*text=gui->addStaticText(L"      ", rect<s32>(0,0,500,200));
	u32 lastGoodUpdateRate=0;
	u32 fpsLastCheckedTime=currentTime;
	fpsTimer.start();
	while(context.getDevice()->run()){
		u32 newTime=timer->getRealTime();
		//u32 speed=timer->getSpeed();
		const f32 frameDeltaTime = (f32)(newTime - currentTime) / 1000.f; // Time in seconds
		context.setCurrentTime(newTime);
		context.setDeltaTime(frameDeltaTime);
		u32 delta=newTime-timeOfLastUpdate;
		updatePhysicsWorld(&context);
		updateOnRenderComponents(&context);
		float milliDelta=fpsTimer.GetSecondes()*2.0f;
		if(milliDelta>=milliseccondsPerUpdate){
			fpsTimer.updateDepart();
			eventReceiver.processMouseSpeed();
			updateUpdateableObjects(&context);
			timeOfLastUpdate=newTime;
			frameCounter++;
			if((newTime-fpsLastCheckedTime)>=1000){
				lastGoodUpdateRate=frameCounter;
				frameCounter=0;
				fpsLastCheckedTime=newTime;
				wchar_t string[1000];
				s32 fps=context.getDriver()->getFPS();
				swprintf(string,L"frameCounter = %d, deltaTime=%f, FPS=%d", lastGoodUpdateRate, frameDeltaTime,fps);
				text->setText(string);
			}
		}

		updateCameras(&context);

		//RENDERING
		context.getDriver()->beginScene(true, true, SColor(255,127,127,127));
		context.getSceneManager()->drawAll();
		gui->drawAll();

		if(debugPhysics==true){
			context.getDriver()->setTransform(irr::video::ETS_WORLD, irr::core::IdentityMatrix);
			context.getCollisionWorld()->debugDrawWorld();
		}

		if(debugNavmesh){
			context.getNavmeshManager()->drawNavmesh(context.getDriver());
		}

		context.getDriver()->endScene();
		currentTime=newTime;
	}
	}catch(char*obj){
		printf("Exception ex %s\n", obj);
	}
	exit(0);
	return 0;
}